﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using GN.Pay.Utils;

namespace GN.Pay.Utils
{
    /// <summary>
    /// 类型帮助
    /// </summary>
    public static class TypeUtils
    {
        /// <summary>
        /// 获取类型默认构造的分配的类型集合
        /// </summary>
        /// <param name="assembly">程序集</param> 
        /// <param name="isReferencedAssemblie">是否引用程序集</param>      
        /// <returns></returns>
        public static List<Type> GetDefaultConstructorAssignableTypes<T>(this Assembly assembly,
            Type attributeType, bool isReferencedAssemblie)
        {
            if (assembly == null)
            {
                throw new ArgumentNullException(nameof(assembly));
            }
            Func<Type, bool> predicate;
            if (attributeType != null)
            {
                predicate = t => !t.IsAbstract && !t.IsInterface && t.GetConstructor(Type.EmptyTypes) != null && t.IsDefined(attributeType, false);
            }
            else
            {
                predicate = t => !t.IsAbstract && !t.IsInterface && t.GetConstructor(Type.EmptyTypes) != null;
            }
            return GetAssignableTypes<T>(assembly, predicate, isReferencedAssemblie);
        }

        /// <summary>
        /// 获取分配类型类型集合
        /// </summary>
        /// <typeparam name="T">类型</typeparam>
        /// <typeparam name="TAttribute">特性类型</typeparam>
        /// <param name="assembly">程序集</param>
        /// <param name="predicate">条件</param>
        /// <param name="isReferencedAssemblie">是否包含引用</param>
        /// <returns></returns>
        public static Dictionary<Type, TAttribute> GetAssignableTypeDictionary<T, TAttribute>(this Assembly assembly, Func<Type, bool> predicate, bool isReferencedAssemblie) where TAttribute : Attribute
        {
            Func<Type, bool> predicate2;
            if (predicate != null)
            {
                predicate2 = t => predicate(t) && t.IsDefined(typeof(TAttribute), false);
            }
            else
            {
                predicate2 = t => t.IsDefined(typeof(TAttribute), false);
            }
            var typeList = GetAssignableTypes<T>(assembly, predicate2, isReferencedAssemblie);
            Dictionary<Type, TAttribute> dic = new Dictionary<Type, TAttribute>();
            foreach (var type in typeList)
            {
                dic[type] = Attribute.GetCustomAttribute(type, typeof(TAttribute)) as TAttribute;
            }
            return dic;
        }

        /// <summary>
        /// 是否是异步方法
        /// </summary>
        /// <param name="method">A method to check</param>
        public static bool IsAsyncMethod(this MethodInfo method)
        {
            return (
                method.ReturnType == typeof(Task) ||
                (method.ReturnType.IsGenericType && method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>))
                );
        }

        /// <summary>
        /// 获取分配类型类型集合
        /// </summary>
        /// <typeparam name="T">类型</typeparam>
        /// <param name="assembly">程序集</param>
        /// <param name="predicate">条件</param>
        /// <param name="isReferencedAssemblie">是否包含引用</param>
        /// <returns></returns>
        public static List<Type> GetAssignableTypes<T>(this Assembly assembly, Func<Type, bool> predicate, bool isReferencedAssemblie)
        {
            if (assembly == null)
            {
                throw new ArgumentNullException(nameof(assembly));
            }
            Type convertType = typeof(T);
            Func<Type, bool> predicate2;
            if (predicate != null)
            {
                predicate2 = t => predicate(t) && convertType.IsAssignableFrom(t);
            }
            else
            {
                predicate2 = t => convertType.IsAssignableFrom(t);
            }
            Type[] types = assembly.GetTypes(predicate2);
            var typeList = new List<Type>(types);
            if (isReferencedAssemblie)
            {
                var assemblies = assembly.GetReferencedAssemblies();
                foreach (var assemblie in assemblies)
                {
                    assembly = Assembly.Load(assemblie);
                    typeList.AddRange(assembly.GetTypes(predicate2));
                }
            }
            return typeList;
        }        

        private static Type[] AssemblyTypes(Assembly assembly)
        {
            try
            {
                Type[] types = assembly.GetTypes();
                return types;
            }
            catch (ReflectionTypeLoadException err)
            {
                if (err.LoaderExceptions.Length > 0)
                {
                    throw err.LoaderExceptions[0];
                }
                throw;
            }
        }

        /// <summary>
        /// 获取类型集合
        /// </summary>
        /// <param name="assembly">程序集</param>
        /// <param name="predicate">条件</param>
        /// <returns></returns>
        public static Type[] GetTypes(this Assembly assembly, Func<Type, bool> predicate)
        {
            if (assembly == null)
            {
                throw new ArgumentNullException(nameof(assembly));
            }
            Type[] types = AssemblyTypes(assembly);
            if (predicate != null)
            {
                return types.Where(predicate).ToArray();
            }
            return types;
        }

        /// <summary>
        /// 是否属于Nullable分配类型
        /// </summary>
        /// <param name="type">类型</param>
        /// <returns></returns>
        public static bool IsNullableType(this Type type)
        {
            ExceptionUtils.CheckNotNull(type, nameof(type));
            return type.IsValueType && type.IsGenericType && typeof(Nullable<>).IsAssignableFrom(type.GetGenericTypeDefinition());
        }

        /// <summary>
        /// 是否属于Nullable分配类型,并输出值类型
        /// </summary>
        /// <param name="type">类型</param>
        /// <param name="valueType">输出值类型</param>
        /// <returns></returns>
        public static bool IsNullableType(this Type type, out Type valueType)
        {
            if (IsNullableType(type))
            {
                valueType = type.GetGenericArguments()[0];
                return true;
            }
            valueType = null;
            return false;
        }

        /// <summary>
        /// 数字类型集
        /// </summary>
        public static readonly HashSet<Type> NumberTypeSet = new HashSet<Type>() {
            typeof(byte),typeof(sbyte),typeof(short),
            typeof(ushort),typeof(int),typeof(uint),
            typeof(long),typeof(ulong),typeof(float),
            typeof(decimal),typeof(double)
        };

        /// <summary>
        /// 基本类型集
        /// </summary>
        public static readonly HashSet<Type> BaseTypeSet = new HashSet<Type>(NumberTypeSet) {
            typeof(string),typeof(bool),typeof(char),
            typeof(Guid),typeof(DateTime),typeof(TimeSpan),
            typeof(DateTimeOffset)
        };



        private static bool IsSetTypeOrNullableDefinitionSetType(Type type,
            ISet<Type> comparerSet, out Type valueType)
        {
            ExceptionUtils.CheckNotNull(type, nameof(type));
            if (comparerSet.Contains(type))
            {
                valueType = type;
                return true;
            }
            if (IsNullableType(type, out valueType))
            {
                return comparerSet.Contains(valueType);
            }
            return false;
        }

        /// <summary>
        /// 获取是否是数字类型
        /// </summary>
        /// <param name="type">类型</param>
        /// <returns></returns>
        public static bool IsNumberType(this Type type)
        {
            ExceptionUtils.CheckNotNull(type, nameof(type));
            return NumberTypeSet.Contains(type);
        }

        /// <summary>
        /// 获取是否是数字类型或Nullable定义的数字类型
        /// </summary>
        /// <param name="type">类型</param>
        /// <param name="valueType">值类型</param>
        /// <returns></returns>
        public static bool IsNumberTypeOrNullableDefinitionNumberType(this Type type, out Type valueType)
        {
            return IsSetTypeOrNullableDefinitionSetType(type, NumberTypeSet, out valueType);
        }

        /// <summary>
        /// 获取是否是基本类型
        /// </summary>
        /// <param name="type">类型</param>
        /// <returns></returns>
        public static bool IsBaseType(this Type type)
        {
            ExceptionUtils.CheckNotNull(type, nameof(type));
            return BaseTypeSet.Contains(type);
        }

        /// <summary>
        /// 获取是否是基本型或Nullable定义的基本类型
        /// </summary>
        /// <param name="type">类型</param>
        /// <param name="valueType">值类型</param>
        /// <returns></returns>
        public static bool IsBaseTypeOrNullableDefinitionBaseType(this Type type, out Type valueType)
        {
            return IsSetTypeOrNullableDefinitionSetType(type, BaseTypeSet, out valueType);
        }

        /// <summary>
        /// 获取是否是枚举类型或Nullable定义的枚举
        /// </summary>
        /// <param name="type">类型</param>
        /// <returns></returns>
        public static bool IsEnumOrNullableType(this Type type)
        {
            ExceptionUtils.CheckNotNull(type, nameof(type));
            if (type.BaseType != null && type.BaseType == typeof(Enum))
            {
                return true;
            }
            else
            {
                if (type.IsValueType && type.IsGenericType)
                {
                    return typeof(Nullable<>).IsAssignableFrom(type.GetGenericTypeDefinition())
                        && type.GetGenericArguments()[0].BaseType == typeof(Enum);
                }
                return false;
            }
        }
    }
}
